/**
 * 图鸟内置工具
 * @author jaylen
 */
import { array as isArray } from './testUtils'
import { Arrayable } from './typescript'
import { get, set } from 'lodash-unified'
import string from './async-validate/validator/string'

/**
 * 本算法来源于简书开源代码，详见：https://www.jianshu.com/p/fdbf293d0a85
 * 全局唯一标识符（uuid，Globally Unique Identifier）,也称作 uuid(Universally Unique IDentifier) 
 * 一般用于多个组件之间,给它一个唯一的标识符,或者v-for循环的时候,如果使用数组的index可能会导致更新列表出现问题
 * 最可能的情况是左滑删除item或者对某条信息流"不喜欢"并去掉它的时候,会导致组件内的数据可能出现错乱
 * v-for的时候,推荐使用后端返回的id而不是循环的index
 * @param len uuid的长度
 * @param firstT firstT 将返回的首字母置为"t"
 * @param radix 生成uuid的基数(意味着返回的字符串都是这个基数),2-二进制,8-八进制,10-十进制,16-十六进制
 * @returns 生成的uuid
 */
export const uuid = (len: number = 32, firstT: boolean = true, radix: number = 0): string => {
  const chars = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'.split('')
	let uuid = []
	radix = radix || chars.length

	if (len) {
		// 如果指定uuid长度,只是取随机的字符,0|x为位运算,能去掉x的小数位,返回整数位
		for (let i = 0; i < len; i++) uuid[i] = chars[0 | Math.random() * radix]
	} else {
		let r;
		// rfc4122标准要求返回的uuid中,某些位为固定的字符
		uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-'
		uuid[14] = '4'

		for (let i = 0; i < 36; i++) {
			if (!uuid[i]) {
				r = 0 | Math.random() * 16
				uuid[i] = chars[(i == 19) ? (r & 0x3) | 0x8 : r]
			}
		}
	}
	// 移除第一个字符,并用t替代,因为第一个字符为数值时,该uuid不能用作id或者class
	if (firstT) {
		uuid.shift()
		return 't' + uuid.join('')
	} else {
		return uuid.join('')
	}
}

/**
 * 根据指定的形式去处理字符串
 * @param str 待处理字符串
 * @param type 标记如何处理
 * @returns 去除空格后的字符串
 */
export const trim = (str: string, type: 'both' | 'left' | 'right' | 'all'): string => {
  if (type === 'both') {
    return str.replace(/^\s+|\s+$/g, "")
  } else if (type === 'left') {
    return str.replace(/^\s*/g, "")
  } else if (type === 'right') {
    return str.replace(/(\s*$)/g, "")
  } else if (type === 'all') {
    return str.replace(/\s+/g, "")
  } else {
    return str
  }
}

/**
 * 处理带单位的值，如果值传入没有带单位则添加上单位
 * @param value 待处理的值
 * @param unit 单位
 * @param returnEmpty 如果值不存在是否返回空字符串（默认返回空字符串）
 * @returns 带单位的值
 */
export const handleValueWithUnit = (value: number | string, unit: string = 'rpx', returnEmpty: boolean = true):string => {
  if (!value) return returnEmpty ? '' : '0px'
  if (typeof value === 'string' && /(%|px|rpx|auto)$/.test(value)) return value
  return value + unit
}

/**
 * 将驼峰命名的字符串转换为指定连接符来进行连接
 * @param str 待转换的字符串
 * @param replace 连接符
 * @returns 转换后的字符串
 */
export const hump2str = (str: string, replace: string = '_'): string => {
  if (!str || !replace) return ''
  return str.replace(/([A-Z])/g, `${replace}$1`).toLowerCase()
}

/**
 * 将用指定连接符来进行连接的字符串转为驼峰命名的字符串
 * @param str 待转换的字符串
 * @param replace 连接符
 * @returns 转换后的字符串
 */
export const str2hump = (hump: string, replace: string = '_'): string => {
  if (!hump || !replace) return ''
  let reg = RegExp(replace + "(\\w)", "g")
  return hump.replace(reg, function(all:string, letter:string) {
    return letter.toUpperCase()
  })
}

/**
 * 截取指定长度的数值
 * @param value 待截取的数值
 * @param len 截取的长度
 * @param prefixZero 如果只有一位是否添加0
 * @returns 截取后的数值字符串
 */
export const subNumber = (value: string | number, len: number = 2, prefixZero: boolean = true): string => {
  let number: number | string = 0
  // 判断传入的值是什么类型
  if (typeof value === 'string') {
    // 如果为空字符串直接返回
    if (value === '') return value
    number = Number(value)
  } else if (typeof value === 'number') {
    number = value
  }
  if (isNaN(number) || number === 0) return prefixZero ? '00' : '0'
  let maxNumber = Math.pow(10, len) - 1
  if (number > maxNumber) return `${maxNumber}+`
  number = String(number)
  return prefixZero ? ('00' + number).substring(number.length > 2 ? 2 : number.length) : number
}

/**
 * 格式化数字，往数值后添加单位并保留指定位数的小数
 * @param value 待格式化的数值
 * @param digits 保留的小数位数
 * @returns 返回截取带单位后的字符串
 */
export const formatNumberWithQuantityUnit = (value: string | number, digits: number = 2): string => {
  // 数值分割点
  const unitSplit = [
    { value: 1, symbol: ''},
    { value: 1E3, symbol: 'K'},
    { value: 1E4, symbol: 'W'},
  ]
  
  const reg = /\.0+$|(\.[0=9]*[1-9])0+$/
  
  let number = 0
  // 判断是什么类型
  if (typeof value === 'string') {
    number = Number(value)
  } else if (typeof value === 'number') {
    number = value
  }
  
  let i
  for (i = unitSplit.length - 1; i > 0; i--) {
    if (number >= unitSplit[i].value) break
  }
  return (number / unitSplit[i].value).toFixed(digits).replace(reg, "$1") + unitSplit[i].symbol
}

/**
 * 格式化价格字符串
 * @param value 价格数值
 * @param digits 保留小数位数
 * @returns 处理后的字符串
 */
export const formatPrice = (value: string | number, digits: number = 2): string => {
  if (!value) return ''
  if (typeof value === 'string') {
    value = Number(value)
  }

  return value.toFixed(digits)
}

/**
 * 获取指定范围内的随机值
 * @param min 最小值
 * @param max 最大值
 * @param intFlag 是否返回正整数
 * @returns 获取到的随机值
 */
export const getRandom = (min: number, max: number, intFlag: boolean = false): number => {
  if (min >= 0 && max > 0 && max >= min) {
    let gab = max - min
    let random = Math.random() * gab + min
    return intFlag ? Math.floor(random) : random
  } else {
    return 0
  }
}

/**
 * 打乱数组内容
 * @param array 原数组
 * @returns 打乱顺序后的数值
 */
export const getRandomArray = <T = any>(array: T[]): T[] => {
  return array.sort(() => Math.random() - 0.5)
}

/**
 * 深度复制数据
 * @param sourceObj 源数据
 * @returns 拷贝后的数据
 */
export const deepClone = (sourceObj: any): object | Array<any> => {
  if ([null, undefined, NaN, false].includes(sourceObj)) return sourceObj
  if (typeof sourceObj !== 'object' && typeof sourceObj !== 'function') {
    return sourceObj
  }
  var o:any = isArray(sourceObj) ? [] : {}
  for (let i in sourceObj) {
    if (sourceObj.hasOwnProperty(i)) {
      o[i] = typeof sourceObj[i] === 'object' ? deepClone(sourceObj[i]) : sourceObj[i]
    }
  }
  return o
}


let debounceTimer: number | null = null
/**
 * 防抖函数: 在一定时间内只有最后一次操作有效，要有效执行要time毫秒后才能执行
 * @param fn 执行函数
 * @param time 延迟时间
 * @param immediate 是否立即执行
 */
export const debounce = (fn: Function, time: number = 200, immediate: boolean = false): void => {
  // 先清除定时器
  if (debounceTimer !== null) clearTimeout(debounceTimer)
  // 判断是否立即执行一次后在进行后续防抖操作
  if (immediate) {
    const now = !debounceTimer
    debounceTimer = setTimeout(() => {
      debounceTimer = null
    }, time)
    if (now) fn && fn()
  } else {
    // 设置定时器，当最后一次操作后，debounceTimer不会再被清除，所以在延迟time毫秒后执行fn回调方法
    debounceTimer = setTimeout(() => {
      fn && fn()
    }, time)
  }
}

let throttleTimer: number | null = null
let throttleFlag: boolean = false
/**
 * 节流函数: 在一定时间内只触发一次
 * @param fn 执行函数
 * @param time 延迟时间
 * @param immediate 是否立即执行
 */
export const throttle = (fn: Function, time: number = 200, immediate: boolean = false): void => {
  if (immediate) {
    if (!throttleFlag) {
      throttleFlag = true
      // 如果是立即执行，则在time毫秒内开始时执行
      fn && fn()
      throttleTimer = setTimeout(() => {
        throttleFlag = false
      }, time)
    }
  } else if (!throttleFlag) {
    throttleFlag = true
    // 在time毫秒内结束时执行
    throttleTimer = setTimeout(() => {
      throttleFlag = false
      fn && fn()
    }, time)
  }
}

/**
 * 从对象中获取指定字段的数据
 * @param obj 从哪里获取数据
 * @param path 对应的字段
 * @param defaultValue 默认值
 * @returns 数据结果
 */
export const getProp = <T = any>(
  obj: Record<string, any>,
  path: Arrayable<string>,
  defaultValue?: any
): { value: T } => {
  return {
    get value() {
      return get(obj, path, defaultValue)
    },
    set value(val: any) {
      set(obj, path, val)
    },
  }
}